package com.bytz.modules.cms.echop.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import com.bytz.common.exception.BytzBootException;
import com.bytz.modules.bpm.client.entity.ActxProcInstComment;
import com.bytz.modules.bpm.client.service.impl.FlowCommentService;
import com.bytz.modules.cms.common.model.EChopConfig;
import com.bytz.modules.cms.common.service.CmsConfigService;
import com.bytz.modules.cms.common.util.ExcelUtil;
import com.bytz.modules.cms.domain.util.DecimalFormatUtil;
import com.bytz.modules.cms.domain.util.PriceComputerUtil;
import com.bytz.modules.cms.echop.model.*;
import com.bytz.modules.cms.echop.service.IEChopService;
import com.bytz.modules.cms.echop.sync.EChopFeign;
import com.bytz.modules.cms.order.constant.OrderConstants;
import com.bytz.modules.cms.order.entity.Order;
import com.bytz.modules.cms.order.entity.OrderItem;
import com.bytz.modules.cms.order.service.IOrderService;
import com.bytz.modules.cms.product.entity.ProductHierarchy;
import com.bytz.modules.cms.product.service.IProductHierarchyService;
import com.bytz.modules.system.entity.SysLog;
import com.bytz.modules.system.service.ICommonService;
import com.bytz.modules.system.service.ISysLogService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.google.common.collect.Lists;
import feign.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class EChopServiceImpl implements IEChopService {

    @Autowired
    private EChopFeign eChopFeign;

    @Autowired
    private FlowCommentService flowCommentService;

    @Autowired
    private IProductHierarchyService hierarchyService;

    @Autowired
    private IOrderService orderService;

    @Autowired
    private CmsConfigService sysConfigService;

    @Autowired
    private ICommonService commonService;

    @Autowired
    private ISysLogService sysLogService;

    @Override
    @Retryable(value = BytzBootException.class, backoff = @Backoff(60000))
    public void generateContract(Order order, List<OrderItem> items) {

        EChopConfig config = sysConfigService.getEChopConfig();

        // 添加审批记录document
        CreateDocumentByFileRequest createDocumentByFileRequest = generateApproveAttach(order);
        Long attachDocumentId = null;
        // 无审批记录的订单不走这一步
        if (createDocumentByFileRequest != null) {
            CreateDocumentByFileResult createDocumentByFileResult = eChopFeign.createDocumentByFile(createDocumentByFileRequest);

            if (!"SUCCESS".equals(createDocumentByFileResult.getMessage())) {
                throw new BytzBootException("添加审批记录document失败" + createDocumentByFileResult.getMessage());
            }

            // 附件添加到e-chop后，删除cms服务器中的附件文件
            createDocumentByFileRequest.deleteFile();

            attachDocumentId = Long.parseLong(createDocumentByFileResult.getResult().getDocumentId());
            orderService.saveEChopId(order.getId(), attachDocumentId, null);
        }

        // 生成合同参数
        ContractDocBo contractDocBo = generateCreateDocumentRequest(order, items);
        // 生成合同
        CreateContractRequest createContractRequest = generateCreateContractRequest(order, contractDocBo, attachDocumentId, config);
        CreateContractResult createContractResult = eChopFeign.createContractByCategory(createContractRequest);
        if (!"SUCCESS".equals(createContractResult.getMessage())) {
            throw new BytzBootException("e-chop生成合同失败" + createContractResult.getMessage());
        }
        log.info("===========合同创建成功，ContractResult={}", createContractResult);
    }

    @Recover
    public void recoverException(Exception exception, Order order) {
        log.error("e-chop生成合同失败，orderId={}，{}", order.getId(), exception);
        SysLog sysLog = new SysLog();
        sysLog.setLogContent("订单" + order.getContractNumber() + "生成合同失败，请手动上传合同->" + exception.getMessage());
        sysLogService.save(sysLog);
        // 异常将合同id更成-1，状态更新成【审核通过】
        orderService.saveEChopId(order.getId(), -1L, OrderConstants.OrderStatus.SHEN_HE_TONG_GUO);
    }

    /**
     * 生成创建合同的请求体
     *
     * @param order 订单
     * @param items 订单子项
     * @return e-chop创建合同的请求体
     */
    private ContractDocBo generateCreateDocumentRequest(Order order, List<OrderItem> items) {

        Set<String> productIds = items.stream().map(OrderItem::getProductId).collect(Collectors.toSet());
        Map<String, ProductHierarchy> hierarchyMap = hierarchyService.getByProductIds(productIds);

        ContractDocBo params = BeanUtil.copyProperties(order, ContractDocBo.class);

        List<ContractItemDocBo> itemBoList = new ArrayList<>();
        for (int i = 0; i < items.size(); i++) {
            ContractItemDocBo contractItemDoc = new ContractItemDocBo();
            OrderItem orderItem = items.get(i);

            contractItemDoc.setSortNumber(String.format("%02d", i + 1));
            contractItemDoc.setProductNumber(orderItem.getProductNumber());
            contractItemDoc.setProductDesc(orderItem.getProductDesc());
            contractItemDoc.setProductNum(String.valueOf(orderItem.getProductNum()));
            contractItemDoc.setUnit(hierarchyMap.get(orderItem.getProductId()).getUnit());

            BigDecimal totalTaxPrice = orderItem.getTotalTaxPrice();
            contractItemDoc.setTotalTaxPrice(DecimalFormatUtil.formatMoney(totalTaxPrice));

            BigDecimal includeTaxPrice = PriceComputerUtil.divide(totalTaxPrice, BigDecimal.valueOf(orderItem.getProductNum()));
            contractItemDoc.setIncludeTaxPrice(DecimalFormatUtil.formatMoney(includeTaxPrice));

            BigDecimal noTaxPrice = PriceComputerUtil.divide(includeTaxPrice, BigDecimal.ONE.add(order.getTaxRate()));
            contractItemDoc.setNoTaxPrice(DecimalFormatUtil.formatMoney(noTaxPrice));

            itemBoList.add(contractItemDoc);
        }
        params.setItems(itemBoList);

        return params;
    }

    /**
     * 生成审批记录附件，封装在请求里
     * 订单无审批流程时返回null
     *
     * @param order 订单
     * @return 添加审批记录附件用到的请求体
     */
    private CreateDocumentByFileRequest generateApproveAttach(Order order) {

        List<OrderApproveExcel> excelData;

        if (StringUtils.isNotBlank(order.getProcInstId())) {
            // 订单审批记录附件
            List<ActxProcInstComment> procInstComments = flowCommentService.queryList(order.getProcInstId());
            excelData = BeanUtil.copyToList(procInstComments, OrderApproveExcel.class);
            excelData.sort(Comparator.comparing(OrderApproveExcel::getEndDate));
            for (int i = 0; i < excelData.size(); i++) {
                excelData.get(i).setSortNumber(i + 1);
            }
        } else {
            return null;
        }

        File file = new File(String.format(OrderConstants.ContractDoc.APPROVE_ATTACH_FILE_NAME, order.getContractNumber()));
        try {
            ExcelUtil.exportToOutputStream("Sheet1", OrderApproveExcel.class, excelData, FileUtil.getOutputStream(file));
        } catch (Exception e) {
            throw new BytzBootException(e);
        }
        return new CreateDocumentByFileRequest(file);
    }

    /**
     * 生成合同的请求
     *
     * @param order            订单
     * @param attachDocumentId e-chop返回的审批记录附件文档id(可空)
     * @param config           系统参数
     * @return e-chop生成合同用的请求体
     */
    private CreateContractRequest generateCreateContractRequest(Order order, ContractDocBo docBo, Long attachDocumentId, EChopConfig config) {
        CreateContractRequest request = new CreateContractRequest();
        request.setBizId(order.getId());
        request.setSubject(String.format(OrderConstants.ContractDoc.FILE_NAME_WITHOUT_SUFFIX, order.getContractNumber()));
        request.setSn(order.getContractNumber());
        request.setDocuments(new ArrayList<>());
        request.setCategoryId(config.getCategoryId());
        request.setSignatories(Lists.newArrayList(new CreateContractRequest.Signatory(
                config.getSignatoryContract(),
                config.getSignatoryTenantName(),
                config.getSignatoryTenantType(),
                config.getSignatorySerialNo()
        )));
        request.setDocumentParams(convertParams(docBo));
        // 添加无审批流后缀或添加审批记录附件
        if (attachDocumentId != null) {
            request.getDocuments().add(attachDocumentId);
        } else {
            request.setSubject(request.getSubject() + "(CMS无审批流)");
        }
        return request;
    }

    @SuppressWarnings("unchecked")
    private List<CreateContractRequest.DocumentParam> convertParams(ContractDocBo contractDocBo) {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        Map<String, String> map = objectMapper.convertValue(contractDocBo, Map.class);
        return map.entrySet().stream()
                .map(entry -> new CreateContractRequest.DocumentParam(entry.getKey(), StringUtils.isBlank(entry.getValue()) ? "-" : entry.getValue()))
                .collect(Collectors.toList());
    }

    @Override
    public void getContractByOrderId(String id, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Order order = orderService.getById(id);
        if (order.getContractSignedFile() != null) {
            // 手动上传了合同，使用手动的
            commonService.download(order.getContractSignedFile().split(":")[0], request, response);
            return;
        }
        if (order.getEChopDocumentId() == null) {
            throw new BytzBootException("订单没有合同生成记录");
        }
        Response feignResponse = eChopFeign.downloadContract(String.valueOf(order.getEChopDocumentId()));
        InputStream inputStream = feignResponse.body().asInputStream();
        Optional<String> disposition = feignResponse.headers().get("Content-Disposition").stream().findFirst();
        disposition.ifPresent(s -> response.addHeader("Content-Disposition", s));
        ServletOutputStream outputStream = response.getOutputStream();
        byte[] buf = new byte[1024];
        int lenth;
        while ((lenth = inputStream.read(buf)) > 0) {
            outputStream.write(buf, 0, lenth);
        }
    }


}
